#include "stdio.h"
#include "fatfs.h"
#include "config.h"
#include "c2_flash.h"
#include "Programmer.h"

// C2 status return codes
#define  INVALID_COMMAND   0x00
#define  COMMAND_FAILED    0x02
#define  COMMAND_OK        0x0D

// C2 interface commands
#define  GET_VERSION       0x01
#define  BLOCK_READ        0x06
#define  BLOCK_WRITE       0x07
#define  PAGE_ERASE        0x08
#define  DEVICE_ERASE      0x03

// C2 Registers
#define  FPDAT             0xB4
#define  FPCTL             0x02
#define  DEVICEID          0x00
#define  REVID             0x01

void Delay_us(uint8_t Nus)
{
    Nus*=10;
    while(Nus--);
}
/****************************************************************************************
-
-   函数名: void StrobeC2CK(void)
-   功能  : C2CK脉冲
-   输入  : 无
-   返回  : 无
-   条件  :   
-
****************************************************************************************/
void StrobeC2CK(void)
{
    T_CLK_0();
    T_CLK_1();
}

/****************************************************************************************
-
-   函数名: void C2_Reset(void)
-   功能  : 将C2CK引脚拉低至>20us复位目标设备
-   输入  : 8位数据
-   返回  : 无
-   条件  :   
-
****************************************************************************************/
void C2_Reset(void)
{
	T_CLK_0();               // Put target device in reset state by pulling
	Delay_us(50);        // C2CK low for >20us
	T_CLK_1();               // Release target device from reset
}

/****************************************************************************************
-
-   函数名: void C2_ReadAR(uint8_t *ReadData)
-   功能  : 读地址寄存器的数据,
-   输入  : 
-   返回  : *ReadData:返回数据指针
-   条件  :   
-
****************************************************************************************/
void C2_ReadAR(uint8_t *ReadData)
{
    uint8_t i,Ch;

    // START field
    StrobeC2CK();                    // Strobe C2CK with C2D driver disabled
   
    // INS field (10b, LSB first)            
    ICP_DAT_OUT_ENABLE();           // Enable C2D driver (output)
	T_DAT_0(); 
    StrobeC2CK();
    T_DAT_1();
    StrobeC2CK();
    ICP_DAT_OUT_DISABLE();     		// Disable C2D driver (input)

    // ADDRESS field
    memset(ReadData,0,EnableChNum);
    for (i=0;i<8;i++)             	// Shift in 8 bit ADDRESS field
    {                            	// LSB-first
        for(Ch = 0; Ch < EnableChNum; Ch++)
        {
            ReadData[Ch] >>= 1;
        }
        StrobeC2CK();
        for(Ch = 0; Ch < EnableChNum; Ch++)
        {
            if(ICP_DAT_Read(Ch))
            {
                ReadData[Ch] |= 0x80;
            }
        }
    }              

    // STOP field
    StrobeC2CK();          			// Strobe C2CK with C2D driver disabled
}                  

/****************************************************************************************
-
-   函数名: void C2_ReadDR(uint8_t *ReadData)
-   功能  : 读数据寄存器的数据
-   输入  : 无
-   返回  : *ReadData:返回数据指针
-   条件  :   
-
****************************************************************************************/
void C2_ReadDR(uint8_t *ReadData)
{
    uint8_t i;

    // START field
    StrobeC2CK();					// Strobe C2CK with C2D driver disabled

    // INS field (00b, LSB first)
    ICP_DAT_OUT_ENABLE();        	// Enable C2D driver (output)
	T_DAT_0();
    StrobeC2CK();
    T_DAT_0();
    StrobeC2CK();

    // LENGTH field (00b -> 1 byte)
    T_DAT_0();
    StrobeC2CK();
    T_DAT_0();
    StrobeC2CK();

    // WAIT field
    ICP_DAT_OUT_DISABLE();       	// Disable C2D driver for input
    do
    {
        StrobeC2CK();
    }
    while (!ICP_DAT_Read(0));      	// Strobe C2CK until target transmits a '1'

    // DATA field
    memset(ReadData,0,EnableChNum);
    for (i=0;i<8;i++)               // Shift in 8-bit DATA field
    {                               // LSB-first
        ReadData[0] >>= 1;
        StrobeC2CK();
        if(ICP_DAT_Read(0))
        {
            ReadData[0] |= 0x80;
        }
   }
    
   // STOP field
   StrobeC2CK();             		// Strobe C2CK with C2D driver disabled
}

/****************************************************************************************
-
-   函数名: void C2_WriteAR(uint8_t addr)
-   功能  : 向地址寄存器写入数据
-   输入  : 地址
-   返回  : 无
-   条件  :   
-
****************************************************************************************/
void C2_WriteAR(uint8_t addr)
{
	uint8_t i;

	// START field
	StrobeC2CK();                   // Strobe C2CK with C2D driver disabled

	// INS field (11b, LSB first)    
	ICP_DAT_OUT_ENABLE();           // Enable C2D driver (output)
	T_DAT_1();
	StrobeC2CK(); 
	T_DAT_1();
	StrobeC2CK();

	// ADDRESS field
	for(i=0;i<8;i++)				// Shift out 8-bit ADDRESS field
	{
		if(addr & 0x01)
		{
			T_DAT_1();
		}
		else
		{
			T_DAT_0();
		}
		StrobeC2CK();
		addr >>= 1;
	}
	ICP_DAT_OUT_DISABLE();			// Disable C2D driver
	StrobeC2CK();            		// Strobe C2CK with C2D driver disabled
}

/****************************************************************************************
-
-   函数名: void C2_WriteDR(uint8_t dat)
-   功能  : 写数据寄存器
-   输入  : 8位数据
-   返回  : 无
-   条件  :   
-
****************************************************************************************/
void C2_WriteDR(uint8_t dat)
{
	uint8_t i;
   
    // START field
    StrobeC2CK();          			// Strobe C2CK with C2D driver disabled

    // INS field (01b, LSB first)
    ICP_DAT_OUT_ENABLE();      		// Enable C2D driver
	T_DAT_1();
    StrobeC2CK();
    T_DAT_0();
    StrobeC2CK();
   
    // LENGTH field (00b -> 1 byte)
    T_DAT_0();
    StrobeC2CK();
    T_DAT_0();
    StrobeC2CK();

    // DATA field
	for (i=0;i<8;i++)            	// Shift out 8-bit DATA field
    {                               // LSB-first
        if((dat & 0x01))
        {
            T_DAT_1();
        }
        else
        {
            T_DAT_0();
        }
        StrobeC2CK();
        dat >>= 1;
    }

    // WAIT field
    ICP_DAT_OUT_DISABLE();      	// Disable C2D driver for input
    do
    {
        StrobeC2CK();       		// Strobe C2CK until target transmits a '1'
    }
    while(!ICP_DAT_Read(0));    	// 目前只判断0通道,后面再看吧
	
    // STOP field
    StrobeC2CK();                 	// Strobe C2CK with C2D driver disabled
}

/****************************************************************************************
-
-   函数名: void Poll_OutReady(void)
-   功能  : 等待输出准备好,这里需要等待全部开启的通道都准备好,这可能是一个bug
-   输入  : 无
-   返回  : 无
-   条件  : 无 
-
****************************************************************************************/
void Poll_OutReady(void)
{
	uint32_t WaitTime=0;	// 用于记录开始等待时间
    uint8_t TempData;	// 读取的标志位
	
    WaitTime =  HAL_GetTick();	//记录开启等待时间
	//全盘擦除大概需要265ms,先设置等待时间不能超过300ms,可能这里需要重新调整
    while((HAL_GetTick()-WaitTime)<300)
    {
        C2_ReadAR(&TempData);// 读取状态标志位
        
        if((TempData&0x01) == 0x01)
        {
            break;
        }
    }
}

/****************************************************************************************
-
-   函数名: void Poll_InBusy(void)
-   功能  : 等待输入确认,这里需要等待全部开启的通道都确认好,这可能是一个bug
-   输入  : 无
-   返回  : 无
-   条件  : 无 
-
****************************************************************************************/
void Poll_InBusy(void)
{
	uint32_t WaitTime=0;	// 用于记录开始等待时间
    uint8_t TempData;	// 读取的标志位
	
    WaitTime =  HAL_GetTick();	//记录开启等待时间
	//全盘擦除大概需要265ms,先设置等待时间不能超过300ms,可能这里需要重新调整
    while((HAL_GetTick()-WaitTime)<300)
    {
        C2_ReadAR(&TempData);// 读取状态标志位

        if((TempData & 0x02) == 0)
        {
            break;
        }
    }
}

/****************************************************************************************
-
-   函数名: uint8_t C2_GetDevID(uint8_t *ReadID)
-   功能  : 读取开启通道目标设备ID
-   输入  : 无
-   返回  : 无
-
****************************************************************************************/
void C2_GetDevID(uint8_t *ReadID)
{
    C2_WriteAR(DEVICEID);//发送ID寄存器地址
    C2_ReadDR(ReadID);   //读取返回的数据
}

/****************************************************************************************
-
-   函数名: void C2_FlashProgram_Init(void)
-   功能  : 初始化用于FLASH编程的C2接口
-   输入  : 无
-   返回  : 无
-   条件  :   
-
****************************************************************************************/
void C2_FlashProgram_Init(void)
{
	C2_Reset();                         // Reset the target device
	Delay_us(4);                    // Delay for at least 2us

	C2_WriteAR(FPCTL);                  // Target the C2 FLASH Programming
										// Control register (FPCTL) for C2 Data 
										// register accesses

	C2_WriteDR(0x02);                   // Write the first key code to enable 
										// C2 FLASH programming
	C2_WriteDR(0x01);                   // Write the second key code to enable 
										// C2 FLASH programming

	HAL_Delay(40);                   // Delay for at least 20ms to ensure the
										// target is ready for C2 FLASH programming
}

/****************************************************************************************
-
-   函数名: char C2_EraseChip(void)
-   功能  : 全盘擦除目标设备的Flash
-   输入  : 
-   返回  : 1:擦除成功;0:擦除失败
-   条件  :   
-
****************************************************************************************/
uint8_t C2_EraseChip(void)
{
    uint8_t status;

    C2_WriteAR(FPDAT);                  // Select the FLASH Programming Data register 
                                        // for C2 Data register accesses
    C2_WriteDR(DEVICE_ERASE);           // Send Device Erase command
    Poll_InBusy();                      // Wait for input acknowledge

    // Check status before starting FLASH access sequence
    Poll_OutReady();                    // Wait for status information
    C2_ReadDR(&status);                  // Read FLASH programming interface status
    
    if(status != COMMAND_OK)
    {
        return 0;
    }
	
   // Send a three-byte arming sequence to enable the device erase. If the sequence
   // is not received correctly, the command will be ignored.
   // Sequence: 0xDE, 0xAD, 0xA5.
   C2_WriteDR(0xDE);                   // Arming sequence command 1
   Poll_InBusy();                      // Wait for input acknowledge

   C2_WriteDR(0xAD);                   // Arming sequence command 2
   Poll_InBusy();                      // Wait for input acknowledge

   C2_WriteDR(0xA5);                   // Arming sequence command 3
   Poll_InBusy();                      // Wait for input acknowledge

   Poll_OutReady();                    // Wait for erase operation to complete
   return 1;                           // Exit and indicate success
}

/****************************************************************************************
-
-   函数名: uint8_t C2_BlockWrite(uint32_t FlashAddr, uint16_t FlashLen, uint8_t *Buff)
-   功能  : 向目标设备的Block写数据
-   输入  : FlashAddr: Flash开始地址,
-           FlashLen:  读取的字节数,
-			c2_ptr:    数据返回指针,
-   返回  : 1:读取成功;0读取失败
-   条件  : 在调用前,确保已经启用了通过C2访问FLASH   
-
****************************************************************************************/
uint8_t C2_BlockWrite(uint32_t FlashAddr, uint16_t FlashLen, uint8_t *Buff)
{
	uint16_t i;
    uint8_t status;

    C2_WriteAR(FPDAT);				// Select the FLASH Programming Data register 
									// for C2 Data register accesses
    C2_WriteDR(BLOCK_WRITE);    	// Send FLASH block write command
    Poll_InBusy();               	// Wait for input acknowledge

    // Check status before starting FLASH access sequence
    Poll_OutReady();            	// Wait for status information
	C2_ReadDR(&status);      		// Read FLASH programming interface status
	
    if(status != COMMAND_OK)
    {
        return 0;
    }

    C2_WriteDR(FlashAddr >> 8);		// Send address high byte to FPDAT
    Poll_InBusy();                  // Wait for input acknowledge
    C2_WriteDR(FlashAddr & 0x00FF); // Send address low byte to FPDAT
    Poll_InBusy();                 	// Wait for input acknowledge
    // 当写入256字节时，长度数据为0.见
    // https://www.silabs.com/documents/public/application-notes/AN127.pdf Table 3.2
    if(FlashLen >= 256)             
        C2_WriteDR(0);              	// Send block size
    else
        C2_WriteDR(FlashLen);           // Send block size
    Poll_InBusy();                  // Wait for input acknolwedge

    // Check status before writing FLASH block
    Poll_OutReady();                // Wait for status information
	C2_ReadDR(&status);              // Read FLASH programming interface status
	
    if(status != COMMAND_OK)
    {
        return 0;
    }
	
    // Write FLASH block
    for (i=0;i<FlashLen;i++)
    {
        C2_WriteDR(*Buff++);         // Write data to the FPDAT register
        Poll_InBusy();               // Wait for input acknowledge
    }   

	Poll_OutReady();               	 // Wait for last FLASH write to complete
    return 1;                        // Exit and indicate success
}

/**********************************************************************************************************
-
-   函数名: uint8_t C2_FLASH_ProgramBuf(uint32_t FlashAddr, uint8_t *_Buff, uint32_t FlashLen)
-   功能  : 通过C2协议对目标Flash进行编程,
-   输入  : FlashAddr:编程开始地址;_Buff:编程数据地址;FlashLen:编程长度
-   返回  : 1:编程完成,0编程失败
-
**********************************************************************************************************/
uint8_t C2_FLASH_ProgramBuf(uint32_t FlashAddr, uint8_t *_Buff, uint32_t FlashLen)
{
	uint16_t i;
	uint16_t BlockNum = FlashLen/256;
    uint8_t LastLen  = FlashLen%256;
	
	for(i=0;i<BlockNum;i++)	// 先烧写1个Block,
    {
		if(C2_BlockWrite(FlashAddr,256,&_Buff[i * 256]) == 0)
		{
			return 0;		// 烧录失败
		}
		FlashAddr += 256;
	}
	
	if(LastLen > 0) 		// 烧录余下的长度
	{
		if(C2_BlockWrite(FlashAddr,LastLen,&_Buff[i * 256]) == 0)
		{
			return 0;		// 烧录失败
		}
	}
	return 1;				// 烧录成功
}

/****************************************************************************************
-
-   函数名: uint8_t C2_ReadBuf(uint32_t ReadAddr, uint8_t (*_Buf)[EnableChNumMax], uint16_t ReadLen)
-   功能  : 读取目标设备Block的数据
-   输入  : ReadAddr:读取的地址;_Buf:返回的二维数据地址;ReadLen:读取长度
-   返回  : 1:读取成功  
-
****************************************************************************************/
uint8_t C2_ReadBuf(uint32_t ReadAddr, uint8_t *_Buf, uint16_t ReadLen)
{
    uint16_t i;
    uint8_t status;

    C2_WriteAR(FPDAT);                  // Select the FLASH Programming Data register 
                       // for C2 Data register accesses
    C2_WriteDR(BLOCK_READ);             // Send FLASH block read command
    Poll_InBusy();                      // Wait for input acknowledge

    // Check status before starting FLASH access sequence
    Poll_OutReady();                    // Wait for status information
	C2_ReadDR(&status);               	// Read FLASH programming interface status
	
    if(status != COMMAND_OK)
    {
        return 0;
    }                   

    C2_WriteDR(ReadAddr >> 8);        	// Send address high byte to FPDAT
    Poll_InBusy();                      // Wait for input acknowledge
    C2_WriteDR(ReadAddr & 0x00FF);    	// Send address low byte to FPDAT
    Poll_InBusy();                      // Wait for input acknowledge
    // 当读取256字节时，长度数据为0.见
    // https://www.silabs.com/documents/public/application-notes/AN127.pdf Table 3.2
    if(ReadLen >= 256)             
        C2_WriteDR(0);              	// Send block size
    else
        C2_WriteDR(ReadLen);              	// Send block size
    Poll_InBusy();                      // Wait for input acknowledge

    // Check status before reading FLASH block
    Poll_OutReady();                    // Wait for status information
	C2_ReadDR(&status);               	// Read FLASH programming interface status
	
    if(status != COMMAND_OK)
    {
        return 0;
    }

    // Read FLASH block
    for (i=0;i<ReadLen;i++)
    {
        Poll_OutReady();                // Wait for data ready indicator
        C2_ReadDR(&_Buf[i]);        		// Read data from the FPDAT register
    }
    return 1;                           // Exit and indicate success
}

/**********************************************************************************************************
-
-   函数名: void C2_FLASH_Verify(uint32_t VerifyAddr,uint8_t *_Buff,uint32_t VerifyLen,uint8_t *Status)
-   功能  : 对C2目标进行校验,
-   输入  : 
-			VerifyAddr:编程数据地址;			*_Buff:首个数据的地址
-			VerifyLen:编程长度,从0地址开始算;	Status:校验状态
-   返回  : 无
-
**********************************************************************************************************/
void C2_FLASH_Verify(uint32_t VerifyAddr,uint8_t *_Buff,uint32_t VerifyLen,uint8_t *Status)
{
    uint8_t VerifyCode[256];
    uint8_t ReadNum = VerifyLen/256;
    uint8_t ReadLastLen = VerifyLen%256;
    uint8_t i;
    uint32_t j;
    
    // 一次校验1个Block
    for(i=0;i<ReadNum;i++)
    {
        C2_ReadBuf(VerifyAddr,VerifyCode,256);

        for(j=0;j<256;j++)
        {
            if(_Buff[i*256 + j] != VerifyCode[j])
            {
                Status[0] = 0;//记录出现问题的通道
            }
        }
		VerifyAddr+=256;
    }
    //余下的校验
    if(ReadLastLen != 0x00)
    {
        C2_ReadBuf(VerifyAddr,VerifyCode,ReadLastLen);
        for(j=0;j<ReadLastLen;j++)
        {
            if(_Buff[i*256 + j] != VerifyCode[j])
            {
                Status[0] = 0;//记录出现问题的通道
            }
        }
    }
}

/**********************************************************************************************************
-
-   函数名: void C2_Programmer(FlashType Target)
-   功能  : 烧写目标
-   输入  : Target:烧写信息结构体地址
-   返回  : 无
-
**********************************************************************************************************/
void C2_Programmer(FlashType *Target)
{
    static  FIL     Readfsrc;
    static  FRESULT Readres;
    uint8_t C2_ID;
    uint32_t sizelen=0;
    uint32_t FileLen=0;
    uint32_t StartTime=0;
	uint32_t FlashLen=0x00;
	uint32_t FlashAdrr=0x00;

    C2_FlashProgram_Init();	// 进入C2模式
    C2_GetDevID(&C2_ID);    //读取芯片ID
    //判断ID是否合理和芯片是否未烧录
    if(C2_ID != 0xff && Target->Status[0] == 0)
    {
#ifdef DebugPrintf
        //显示ID
        printf("C2_ID :%x\n",C2_ID);
#endif
        Target->Start = 1; //检测有需要烧录的芯片,启动烧录
    }
    //判断ID不合理，说明没有连接芯片，复位烧录标志位
    if(C2_ID == 0xff)
    {
        memset(Target->Status,0,EnableChNum);// 清除标志位
    }
	
	if(Target->Start == 1)
	{
        LED0_GPIO_Port->BSRR = (uint32_t)LED0_Pin << 16u;
		ProgrammerInit();		// 因为有用到C2CK线的读取功能,每次都需要重新初始化IO口
		C2_Reset();				// 复位
		C2_FlashProgram_Init();	// 进入C2模式
		StartTime = HAL_GetTick(); //获取时间
        // 全盘擦除
        if(C2_EraseChip() != 1)		
        {
			Target->Start = 0;  // 擦除失败,标记烧录失败
            memset(Target->Status,0,EnableChNum);// 清除标志位
			return;
        }
		
		FlashAdrr = 0;			// 初始化烧录地址
		Readres = f_open( &Readfsrc ,"0:/FW/release.bin", FA_OPEN_ALWAYS | FA_READ);//打开工程文件
        FileLen = f_size(&Readfsrc);
		f_lseek(&Readfsrc,0);   //设置读取指针指向固件地址
		if ( Readres == FR_OK )
		{
            memset(Target->Status,1,EnableChNum);// 提前标志校验成功,在校验失败时,会置0
			memset(Target->FW_Buf,0xff,StorageLen);
			while(1)// 循环烧录,直到烧录完成
			{
				// 开始读取FW文件,一直读取完,读完后,再次读取时,读出的字节会为0
				Readres = f_read(&Readfsrc,Target->FW_Buf,StorageLen,&sizelen);//一次读取1次固件包
				if(sizelen > 32)
				{
                    FlashAdrr = Target->FW_Buf[0]<<24 | Target->FW_Buf[1]<<16 | Target->FW_Buf[2]<<8 | Target->FW_Buf[3];
					FlashLen = Target->FW_Buf[4]<<24 | Target->FW_Buf[5]<<16 | Target->FW_Buf[6]<<8 | Target->FW_Buf[7];
					C2_FLASH_ProgramBuf(FlashAdrr,&Target->FW_Buf[StorageFwStart],FlashLen);
					C2_FLASH_Verify(FlashAdrr,&Target->FW_Buf[StorageFwStart],FlashLen,Target->Status);
#ifdef DebugPrintf
                    //显示烧录进度
                    printf("ProgrammProgress :%.2f%c Time :%.2fS\n",100.0*Readfsrc.fptr/FileLen,'%',(HAL_GetTick()-StartTime)*0.001);
#endif
				}
				else
				{
					break;// 退出循环烧录
				}
				if(Target->Status[0] == 0)
				{
					memset(Target->Status,0,EnableChNum);// 清除标志位
					break;
				}
			}
            for(uint8_t Ch=0;Ch<EnableChNum;Ch++)// 成功数累计
            {
                if(Target->Status[Ch] == 1)
                {
                    Target->FlashOk[Ch] += 1;
                }
            }
            C2_Reset();// 烧录完成,复位
		}
		else
		{
            memset(Target->Status,0,EnableChNum);//读取SD卡固件失败,标记烧录失败
		}
		f_close(&Readfsrc);// 关闭文件
        
		if(Target->Status[0] == 1)
		{
#ifdef DebugPrintf
            //显示烧录成功的数量
            printf("FlashOk :%d\n",(int)Target->FlashOk[0]);
            SaveProgrammNum(Target);
#endif
		}
        Target->Start = 0;
    }
}

